using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using OptixDotNet;
using OptixDotNet.Math;
using OptixDotNet.Traversal;
using RayDen.Library.Components.SystemComponents;
using RayDen.RayEngine.Core.Devices;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using Buffer = OptixDotNet.Buffer;
using RayHit = RayDen.Library.Core.RayHit;

namespace RayDen.RayEngine.IntersectionEngines.Optix
{
    public class OptixTraverseIntersectionDevice : IntersectionEngine
    {

        protected new RayEngineScene scene;
        protected Stopwatch wallclock;
        protected double statsDeviceIdleTime;
        protected double statsDeviceTotalTime;
        protected Task intersectionTask;
        protected CancellationTokenSource cancelToken;

        protected InputRayBufferCollection todoRayBuffers;
        protected OutputRayBufferCollection doneRayBuffers;


        protected Buffer RayHits;
        protected Buffer Rays;
        protected GeometryGroup GeoGroup { get; set; }

        protected AccelBuilder Builder { get; set; }
        protected AccelTraverser Traverser { get; set; }
        protected Context Context = null;

        protected Traversal trav;


        protected TriInfo[] triangles;
        protected float[] td;
        public OptixTraverseIntersectionDevice(IRayEngineScene scene, bool lowLatency = false)
            : base(scene)
        {
            this.lowLatency = lowLatency;
            Context = new Context { CpuNumThreads = 4, RayTypeCount = 1, EntryPointCount = 1 };


            this.todoRayBuffers = new InputRayBufferCollection();
            this.doneRayBuffers = new OutputRayBufferCollection();
            this.started = false;

            this.SetScene(scene);
        }

        public override void SetScene(IRayEngineScene secn)
        {

            Tracer.TraceLine("OptixIntersectionDevice  -  Initializing Optix");
            this.scene = (RayEngineScene)secn;
            td = scene.Triangles.SelectMany(tr => new[]
            {
                scene.Vertices[tr.v0.VertexIndex].x,
                scene.Vertices[tr.v0.VertexIndex].y,
                scene.Vertices[tr.v0.VertexIndex].z,
                scene.Vertices[tr.v1.VertexIndex].x,
                scene.Vertices[tr.v1.VertexIndex].y,
                scene.Vertices[tr.v1.VertexIndex].z,
                scene.Vertices[tr.v2.VertexIndex].x,
                scene.Vertices[tr.v2.VertexIndex].y,
                scene.Vertices[tr.v2.VertexIndex].z,

            }).ToArray();
            this.triangles = scene.Triangles.Select(tr => new TriInfo
            {
                v0x = scene.Vertices[tr.v0.VertexIndex].x,
                v1x = scene.Vertices[tr.v1.VertexIndex].x,
                v2x = scene.Vertices[tr.v2.VertexIndex].x,

                v0y = scene.Vertices[tr.v0.VertexIndex].y,
                v1y = scene.Vertices[tr.v1.VertexIndex].y,
                v2y = scene.Vertices[tr.v2.VertexIndex].y,

                v0z = scene.Vertices[tr.v0.VertexIndex].z,
                v1z = scene.Vertices[tr.v1.VertexIndex].z,
                v2z = scene.Vertices[tr.v2.VertexIndex].z,

            }).ToArray();
            if (trav == null)
                trav = new Traversal(Context, QueryType.ClosestHit, RayFormat.OriginDirectionMinMaxInterleaved, TriFormat.TriangleSoup, TraversalOutput.BaryCentric, InitOptions.CullBackFace);

            trav.SetTriangles(td);
        }

        private IEnumerable<Int3> Convert(int[] list)
        {
            for (int i = 0; i < list.Length; i += 3)
            {
                yield return new Int3(list[i], list[i + 1], list[i + 2]);
            }
        }

        public override void PushRayBuffer(RayBuffer rayBuffer, int clientIdx)
        {
            if (clientIdx + 1 > doneRayBuffers.Count)
            {
                while (doneRayBuffers.Count < (clientIdx + 1))
                {
                    doneRayBuffers.AddBuffer();
                }
            }
            todoRayBuffers.Put(new Tuple<int, RayBuffer>(clientIdx, rayBuffer));
        }

        public override RayBuffer PopRayBuffer(int info)
        {
            return doneRayBuffers.Get(info);
        }

        public override void Stop()
        {
            wallclock.Stop();
            started = false;
            wallclock.Restart();
            if (intersectionTask.Status == TaskStatus.Running)
            {
                this.cancelToken.Cancel();
                Tracer.TraceLine("Attempting to stop Optix intersection device");
                while ((intersectionTask.Status == TaskStatus.Running))
                {
                    this.cancelToken.Cancel();
                    if (wallclock.Elapsed > new TimeSpan(0, 0, 30))
                    {
                        break;
                    }
                }
                intersectionTask.Dispose();
                intersectionTask = null;
            }

            this.todoRayBuffers = new InputRayBufferCollection();
            this.doneRayBuffers = new OutputRayBufferCollection();
        }
        public bool lowLatency = false;
        public override void Trace(RayBuffer rayBuffer)
        {
            //if (rayBuffer.RaysInitialized == 0)
            //{
            //    Debugger.Break();
            //}
            var raysArg = rayBuffer.RaysInfo;
            var raysHandle = GCHandle.Alloc(raysArg, GCHandleType.Pinned);

            uint size = lowLatency
                ? (uint)(Math.Sqrt(RayBuffer.RayBufferSize / 8))
                : (uint)(Math.Sqrt(RayBuffer.RayBufferSize));


            trav.SetRayData(rayBuffer.RaysInfo);
            trav.Traverse();
            var result = new TraversalResult[rayBuffer.RaysInfo.Length];
            var coords = new Vector2[rayBuffer.RaysInfo.Length];
            trav.GetResults(result);
            trav.GetBaryCentricOutput(coords);
            rayBuffer.rayHits = result.Select((rh, i) => new RayHit
            {
                Distance = rh.T,
                Index = (uint)rh.T,
                U = coords[i].X,
                V = coords[i].Y,
            }).ToArray();
            rayBuffer.traced = true;
            raysHandle.Free();
        }

        public override void Start()
        {
            if (started)
                return;
            started = true;
            if (wallclock == null)
            {
                wallclock = Stopwatch.StartNew();
            }
            else
            {
                wallclock.Start();
            }
            statsDeviceIdleTime = 0.0;
            statsDeviceTotalTime = 0.0;
            if (cancelToken == null)
            {
                cancelToken = new CancellationTokenSource();
                this.intersectionTask = Task.Factory.StartNew(() => RayIntersection(cancelToken.Token, this),
                    cancelToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
            }
        }

        protected static void RayIntersection(CancellationToken ct, OptixTraverseIntersectionDevice device)
        {
#if VERBOSE
            try
            {
                
#endif
            Tracer.TraceLine("Intersection device {0}", device.GetType().Name);
            while (true)
            {
                if (ct.IsCancellationRequested || device.cancelToken.IsCancellationRequested)
                {
                    Tracer.TraceLine("Stopping intersection device {0}", device.GetType().Name);
                    break;
                }
                var t1 = 0;// device.wallclock.ElapsedTicks;
                Tuple<int, RayBuffer> rayBuffer = null;

#if OLD 
                if (!device.todoRayBuffers.TryDequeue(out rayBuffer))
                    continue;

#else
                rayBuffer = device.todoRayBuffers.Get();
                /*
                if (device.todoRayBuffers.IsEmpty)
                    continue;
                if (!device.todoRayBuffers.TryDequeue(out rayBuffer))
                {
                    continue;
                }
                 * */
#endif

                var t2 = DateTime.UtcNow.Ticks;
                device.Trace(rayBuffer.Item2);
                //Tracer.TraceLine("Result returns {0} items ", buffer.rayHits.Length);
                //            Tracer.TraceLine("Intersections {0} items ", buffer.rayHits.Count(p => p.index != uint.MaxValue));
                var t3 = device.wallclock.ElapsedTicks;

                device.statsDeviceIdleTime += t2 - t1;
                device.statsDeviceTotalTime += t3 - t1;
                device.statsTotalRayCount += rayBuffer.Item2.RaysInfo.Length;
                device.doneRayBuffers.Put(rayBuffer.Item1, rayBuffer.Item2);
                //device.doneRayBuffers[rayBuffer.Item1].Enqueue(rayBuffer.Item2);
            }

#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine("Intersection device exception");
                //Tracer.TraceLine("Error triangle {0}", currentTriangleIndex);
                Tracer.TraceLine(ex.Message);
                Tracer.TraceLine(ex.StackTrace);

            }
#endif
        }

        public override void Dispose()
        {
            if (Context != null)
            {
                trav.Dispose();
                Context.Dispose();
            }
        }
    }
}